<template>
  <view
    :class="`${ns.b()}  ${customClass} ${
      slidableNum < items.length ? 'is-slide' : ''
    } ${mapNum < items.length && mapNum !== 0 ? 'is-map' : ''}`"
  >
    <!--头部导航容器-->
    <view :class="ns.e('nav')">
      <view :class="ns.em('nav', 'wrap')">
        <scroll-view
          :scroll-x="slidableNum < items.length"
          scroll-with-animation
          :scroll-left="state.scrollLeft"
        >
          <view :class="ns.e('nav-container')">
            <!--nav列表-->
            <view
              v-for="(item, index) in items"
              @click="handleSelect(index)"
              :key="index"
              :class="`${ns.e('nav-item')} ${
                state.activeIndex === index ? 'is-active' : ''
              } ${item.disabled ? 'is-disabled' : ''}`"
              :style="
                state.activeIndex === index
                  ? color
                    ? 'color:' + color
                    : ''
                  : inactiveColor
                  ? 'color:' + inactiveColor
                  : ''
              "
            >
              {{ item.title }}
            </view>
            <!--下划线-->
            <view :class="ns.e('line')" :style="state.lineStyle"></view>
          </view>
        </scroll-view>
      </view>
      <!--map表-->
      <view :class="ns.e('map')" v-if="mapNum < items.length && mapNum !== 0">
        <view :class="ns.e('map-btn')" @click="toggleMap">
          <view
            :class="`${ns.e('map-arrow')} ${state.animating ? 'is-open' : ''}`"
          >
            <uni-icons type="down" size="14"></uni-icons>
          </view>
        </view>
        <view
          :class="ns.e('map-header')"
          :style="`${state.mapShow ? '' : 'display:none;'}  ${
            state.animating ? 'opacity:1;' : ''
          }`"
        >
          全部
        </view>
        <view
          :class="`${ns.e('map-body')} ${state.animating ? 'is-open' : ''}`"
          :style="state.mapShow ? '' : 'display:none'"
        >
          <view
            :class="ns.e('map-nav-item')"
            v-for="(item, index) in items"
            :key="index"
            @click="handleSelect(index)"
          >
            <view
              :class="`${ns.e('map-nav-btn')} ${
                state.activeIndex === index ? 'is-active' : ''
              }  ${item.disabled ? 'is-disabled' : ''}`"
            >
              {{ item.title }}
            </view>
          </view>
        </view>
      </view>
    </view>

    <!--标签页-->
    <view
      :class="ns.e('container')"
      @touchstart="onTouchStart"
      @touchmove="onTouchMove"
      @touchend="onTouchEnd"
      @touchcancel="onTouchEnd"
    >
      <view
        :class="[ns.e('body'), animated ? 'is-animated' : '']"
        :style="bodyStyle"
      >
        <slot />
      </view>
    </view>

    <!--map表的阴影浮层-->
    <view
      :class="ns.e('mask')"
      :style="`${state.mapShow ? '' : 'display:none;'}  ${
        state.animating ? 'opacity:1' : ''
      }`"
      @click="toggleMap"
    >
    </view>
  </view>
</template>
<script lang="ts">
export default {
  options: {
    addGlobalClass: true,
    virtualHost: true,
    styleIsolation: "shared"
  }
}
</script>
<script setup lang="ts">
import {
  computed,
  getCurrentInstance,
  onMounted,
  watch,
  nextTick,
  reactive,
  type CSSProperties,
  PropType,
  type ExtractPropTypes,
  type InjectionKey,
} from "vue";
import { isNumber, isString } from "lodash-es";
import {
  useNamespace,
  addUnit,
  checkNumRange,
  debounce,
  getRect,
  isDef,
  objToStyle,
  useChildren,
  useTouch,
} from "@/use";
import { TABS_KEY, tabsProps } from "./i-biz-tabs";

const ns = useNamespace("tabs");
const $item = `.${ns.e("nav-item")}`;
const $container = `.${ns.e("nav-container")}`;

const props = defineProps(tabsProps);

const emit = defineEmits(["change", "disabled", "click", "update:modelValue"]);

const state = reactive({
  activeIndex: 0, // 选中值的索引，默认第一个
  lineStyle: "display:none;", // 激活项边框线样式
  inited: false, // 是否初始化
  animating: false, // 是否动画中
  mapShow: false, // map的开关
  scrollLeft: 0, // scroll-view偏移量
});

// map的开关

const { children, linkChildren } = useChildren(TABS_KEY);
linkChildren({ state });

const { proxy } = getCurrentInstance() as any;

const touch = useTouch();

// tabs数据
const items = computed(() => {
  return children.map((child, index) => {
    return {
      disabled: child.disabled,
      title: child.title,
      name: isDef(child.name) ? child.name : index,
    };
  });
});

const bodyStyle = computed(() => {
  if (!props.animated) {
    return "";
  }

  return objToStyle({
    left: -100 * state.activeIndex + "%",
    "transition-duration": props.duration + "ms",
    "-webkit-transition-duration": props.duration + "ms",
  });
});

/**
 * @description 修改选中的tab Index
 * @param {String |Number } value - radio绑定的value或者tab索引，默认值0
 * @param {Boolean } init - 是否伴随初始化操作
 */
const setActive = debounce(
  function (
    value: number | string = 0,
    init: boolean = false,
    setScroll: boolean = true
  ) {
    // 没有tab子元素，不执行任何操作
    if (items.value.length === 0) return;

    value = getActiveIndex(value);
    // 被禁用，不执行任何操作
    if (items.value[value].disabled) return;
    state.activeIndex = value;
    if (setScroll) {
      updateLineStyle(init === false);
      scrollIntoView();
    }
    setActiveTab();
  },
  100,
  { leading: false }
);

watch(
  () => props.modelValue,
  (newValue) => {
    if (!isNumber(newValue) && !isString(newValue)) {
      console.error(
        "[wot design] error(tabs): the type of value should be number or string"
      );
    }
    // 保证不为非空字符串，小于0的数字
    if (newValue === "" || !isDef(newValue)) {
      // eslint-disable-next-line quotes
      console.error(
        "[wot design] error(tabs): tabs's value cannot be '' null or undefined"
      );
    }
    if (typeof newValue === "number" && newValue < 0) {
      // eslint-disable-next-line quotes
      console.error(
        "[wot design] error(tabs): tabs's value cannot be less than zero"
      );
    }
  },
  {
    immediate: true,
    deep: true,
  }
);

watch(
  () => props.modelValue,
  (newValue) => {
    const index = getActiveIndex(newValue);
    setActive(newValue, false, index !== state.activeIndex);
  },
  {
    immediate: false,
    deep: true,
  }
);

watch(
  () => children.length,
  () => {
    if (state.inited) {
      nextTick(() => {
        setActive(props.modelValue);
      });
    }
  }
);

watch(
  () => props.slidableNum,
  (newValue) => {
    checkNumRange(newValue, "slidableNum");
  }
);

watch(
  () => props.mapNum,
  (newValue) => {
    checkNumRange(newValue, "mapNum");
  }
);

onMounted(() => {
  state.inited = true;
  nextTick(() => {
    setActive(props.modelValue, true);
  });
});

/**
 * @description nav map list 开关
 */
function toggleMap() {
  // 必须保证display和transition不在同一个帧
  if (state.mapShow) {
    state.animating = false;
    setTimeout(() => {
      state.mapShow = false;
    }, 300);
  } else {
    state.mapShow = true;
    setTimeout(() => {
      state.animating = true;
    }, 100);
  }
}

/**
 * @description 更新navBar underline的偏移量
 * @param {Boolean} animation 是否伴随动画
 */
function updateLineStyle(animation: boolean = true) {
  if (!state.inited) return;
  const { lineWidth, lineHeight } = props;
  getRect($item, true, proxy).then((rects) => {
    const lineStyle: CSSProperties = {};

    if (isDef(lineWidth)) {
      lineStyle.width = addUnit(lineWidth);
    }
    if (isDef(lineHeight)) {
      lineStyle.height = addUnit(lineHeight);
      lineStyle.borderRadius = `calc(${addUnit(lineHeight)} / 2)`;
    }
    const rect = rects[state.activeIndex];
    let left =
      rects
        .slice(0, state.activeIndex)
        .reduce((prev, curr) => prev + Number(curr.width), 0) +
      Number(rect.width) / 2;
    lineStyle.transform = `translateX(${left}px) translateX(-50%)`;
    if (animation) {
      lineStyle.transition = "width 300ms ease, transform 300ms ease";
    }
    state.lineStyle = objToStyle(lineStyle);
  });
}
/**
 * @description 通过控制tab的active来展示选定的tab
 */
function setActiveTab() {
  if (!state.inited) return;
  if (items.value[state.activeIndex].name !== props.modelValue) {
    emit("change", {
      index: state.activeIndex,
      name: items.value[state.activeIndex].name,
    });
    emit("update:modelValue", items.value[state.activeIndex].name);
  }
}
/**
 * @description scroll-view滑动到active的tab_nav
 */
function scrollIntoView() {
  if (!state.inited) return;
  Promise.all([
    getRect($item, true, proxy),
    getRect($container, false, proxy),
  ]).then(([navItemsRects, navRect]) => {
    // 选中元素
    const selectItem = navItemsRects[state.activeIndex];
    // 选中元素之前的节点的宽度总和
    const offsetLeft = (navItemsRects as any)
      .slice(0, state.activeIndex)
      .reduce((prev: any, curr: any) => prev + curr.width, 0);
    // scroll-view滑动到selectItem的偏移量
    const left =
      offsetLeft - ((navRect as any).width - Number(selectItem.width)) / 2;
    if (left === state.scrollLeft) {
      state.scrollLeft = left + Math.random() / 10000;
    } else {
      state.scrollLeft = left;
    }
  });
}
/**
 * @description 单击tab的处理
 * @param index
 */
function handleSelect(index: number) {
  if (index === undefined) return;
  const { name, disabled } = items.value[index];
  if (disabled) {
    emit("disabled", {
      index,
      name,
    });
    return;
  }
  state.mapShow && toggleMap();
  setActive(index);
  emit("click", {
    index,
    name,
  });
}
/**
 * @description touch handle
 * @param event
 */
function onTouchStart(event: any) {
  if (!props.swipeable) return;
  touch.touchStart(event);
}
function onTouchMove(event: any) {
  if (!props.swipeable) return;
  touch.touchMove(event);
}
function onTouchEnd() {
  if (!props.swipeable) return;
  const { direction, deltaX, offsetX } = touch;
  const minSwipeDistance = 50;
  if (direction.value === "horizontal" && offsetX.value >= minSwipeDistance) {
    if (deltaX.value > 0 && state.activeIndex !== 0) {
      setActive(state.activeIndex - 1);
    } else if (
      deltaX.value < 0 &&
      state.activeIndex !== items.value.length - 1
    ) {
      setActive(state.activeIndex + 1);
      setActive(state.activeIndex + 1);
    }
  }
}
function getActiveIndex(value: number | string) {
  // name代表的索引超过了items的边界，自动用0兜底
  if (isNumber(value) && value >= items.value.length) {
    // eslint-disable-next-line prettier/prettier
    console.error(
      "[wot design] warning(tabs): the type of tabs' value is Number shouldn't be less than its children"
    );
    value = 0;
  }
  // 如果是字符串直接匹配，匹配不到用0兜底
  if (isString(value)) {
    const index = items.value.findIndex((item) => item.name === value);
    value = index === -1 ? 0 : index;
  }

  return value;
}

defineExpose({
  setActive,
  scrollIntoView,
  updateLineStyle,
});
</script>
<style lang="scss" scoped>
/* 单行超出隐藏 */
/* 单行超出隐藏 */
@mixin lineEllipsis {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 0.5px 边框 指定方向*/
@mixin halfPixelBorder($direction: "bottom", $left: 0) {
  position: relative;

  &::after {
    position: absolute;
    display: block;
    content: "";

    @if ($left==0) {
      width: 100%;
    } @else {
      width: calc(100% - #{$left});
    }

    height: 1px;
    left: $left;

    @if ($direction== "bottom") {
      bottom: 0;
    } @else {
      top: 0;
    }

    transform: scaleY(0.5);
  }
}

@mixin buttonClear {
  outline: none;
  -webkit-appearance: none;
  -webkit-tap-highlight-color: transparent;
  background: transparent;
}

@include b(tabs) {
  $-tabs-nav-height: 42px;
  $-tabs-nav-fs: 14px;
  $-tabs-nav-color: rgba(0, 0, 0, 0.85);
  $-tabs-nav-disabled-color: rgba(0, 0, 0, 0.25);
  $-tabs-nav-line-height: 3px;
  $-tabs-nav-line-width: 19px;
  $-tabs-nav-line-bg-color: #4d80f0;
  $-tabs-nav-map-arrow-color: rgba(0, 0, 0, 0.65);
  $-tabs-nav-bg: rgb(255, 255, 255);
  $-tabs-nav-map-btn-before-bg: linear-gradient(
    270deg,
    rgba(255, 255, 255, 1) 1%,
    rgba(255, 255, 255, 0) 100%
  );
  $-tabs-nav-map-fs: 14px;
  $-tabs-nav-map-color: rgba(0, 0, 0, 0.85);
  $-tabs-nav-map-button-back-color: rgba(0, 0, 0, 0.04);
  $-tabs-nav-map-button-radius: 16px;
  $-tabs-nav-active-color: #4d80f0;
  $-tabs-nav-map-modal-bg: rgba(0, 0, 0, 0.65);
  $-color-border-light: #e8e8e8;

  position: relative;
  background: #fff;
  width: 100%;

  @include e(nav) {
    left: 0;
    right: 0;
    height: $-tabs-nav-height;
    background-color: #fff;
    width: 100%;
    position: relative;

    @include m(wrap) {
      height: 100%;
      overflow: hidden;
    }

    @include m(sticky) {
      width: 100vw;
    }
  }

  @include e(nav-container) {
    position: relative;
    display: flex;
    user-select: none;
  }

  @include e(nav-item) {
    flex: 1;
    min-width: 0;
    text-align: center;
    height: $-tabs-nav-height;
    line-height: $-tabs-nav-height;
    font-size: $-tabs-nav-fs;
    color: $-tabs-nav-color;
    transition: color 0.3s;
    @include lineEllipsis();

    @include when(active) {
      font-weight: 600;
    }

    @include when(disabled) {
      color: $-tabs-nav-disabled-color;
    }
  }

  @include e(line) {
    position: absolute;
    bottom: 4px;
    left: 0;
    z-index: 1;
    height: $-tabs-nav-line-height;
    width: $-tabs-nav-line-width;
    background: $-tabs-nav-line-bg-color;
    border-radius: calc($-tabs-nav-line-height / 2);
  }

  @include e(container) {
    overflow: hidden;
  }

  @include e(body) {
    position: relative;
    width: 100%;
    height: 100%;

    @include when(animated) {
      display: flex;
      transition-property: left;
    }
  }

  @include e(map) {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1;
  }

  @include e(map-btn) {
    position: absolute;
    right: 0;
    top: 0;
    width: $-tabs-nav-height;
    height: $-tabs-nav-height;
    line-height: $-tabs-nav-height;
    text-align: center;
    color: $-tabs-nav-map-arrow-color;
    z-index: 1;
    background: $-tabs-nav-bg;
    -webkit-tap-highlight-color: transparent;

    &::before {
      position: absolute;
      content: "";
      top: 0;
      left: -24px;
      width: 24px;
      height: $-tabs-nav-height - 1;
      background: $-tabs-nav-map-btn-before-bg;
    }
  }

  @include e(map-arrow) {
    display: block;
    transition: transform 0.3s;

    @include when(open) {
      transform: rotate(180deg);
    }
  }

  @include e(map-header) {
    position: relative;
    padding-left: 17px;
    height: $-tabs-nav-height;
    line-height: $-tabs-nav-height;
    font-size: $-tabs-nav-map-fs;
    color: $-tabs-nav-map-color;
    transition: opacity 0.3s;
    background: #fff;
    opacity: 0;

    @include halfPixelBorder;

    &::after {
      z-index: 3;
      background-color: $-color-border-light;
    }
  }

  @include e(map-body) {
    display: flex;
    flex-wrap: wrap;
    padding: 20px 15px 10px;
    background: #fff;
    transition: transform 0.3s;
    transform: scaleY(0);
    transform-origin: center top;

    @include when(open) {
      transform: scaleY(1);
    }
  }

  @include e(map-nav-item) {
    flex-basis: 33%;

    &:nth-child(3n + 2) {
      text-align: center;
    }

    &:nth-child(3n + 3) {
      text-align: right;
    }
  }

  @include e(map-nav-btn) {
    @include buttonClear;
    @include lineEllipsis;
    display: inline-block;
    width: 107px;
    height: 32px;
    line-height: 32px;
    background-color: $-tabs-nav-map-button-back-color;
    border-color: transparent;
    margin-bottom: 10px;
    border-radius: $-tabs-nav-map-button-radius;
    color: $-tabs-nav-map-color;
    font-size: $-tabs-nav-map-fs;
    text-align: center;
    transition: color 0.3s, border-color 0.3s;

    @include when(active) {
      color: $-tabs-nav-active-color;
      border: 1px solid $-tabs-nav-active-color;
      background-color: $-tabs-nav-bg;
    }

    @include when(disabled) {
      color: $-tabs-nav-disabled-color;
      border-color: #f4f4f4;
    }
  }

  @include e(mask) {
    position: absolute;
    top: $-tabs-nav-height;
    left: 0;
    right: 0;
    bottom: 0;
    background: $-tabs-nav-map-modal-bg;
    opacity: 0;
    transition: opacity 0.3s;
  }

  @include when(slide) {
    @include e("nav-item") {
      flex: 0 0 auto;
      padding: 0 18px;
    }
  }

  @include when(map) {
    @include e(nav) {
      @include m(wrap) {
        padding-right: 40px;
      }
    }
  }
}

@media screen and (max-width: 330px) {
  .#{bem(tabs__map-nav-btn)} {
    width: 90px;
  }
}
</style>
